package com.lagou.edu.factory;

import com.lagou.edu.config.annotations.Autowired;
import com.lagou.edu.config.annotations.Service;
import com.lagou.edu.config.annotations.Transactional;
import com.lagou.edu.utils.ScanPackageUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */
    private static Map<String,Object> beansMap = new HashMap<>();
    private static List<String> transactionalList = new ArrayList<>();


    public  BeanFactory(String packageName) throws IOException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, ClassNotFoundException {

        //初始化 beansMap
        checkAnnotationAndInit(ScanPackageUtils.getResources(packageName));

        if (beansMap.isEmpty()){
            throw new RuntimeException("No found beans");
        }
        //autowired
        injectionDependence();

        //Transactional
        setClassProxyObj();
    }

    /**
     * 初始化 beansMap
     * @param classes
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     */
    private void checkAnnotationAndInit(List<Class<?>> classes) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        for (Class<?> classInfo : classes) {
            Service annotation = classInfo.getAnnotation(Service.class);
            Transactional transactional = classInfo.getAnnotation(Transactional.class);
            if (annotation != null ) {
                Object bean = classInfo.getDeclaredConstructor().newInstance();
                String beanName = toLowerCaseFirstOne(classInfo.getSimpleName());
                if (transactional != null) {
                    transactionalList.add(beanName);
                }
                beansMap.put(beanName, bean);
            }
        }
    }

    /**
     * 获取代理对象并替换已有对象
     */
    private void setClassProxyObj() {
        if (transactionalList.isEmpty()) {
            return;
        }

        for (String key : transactionalList) {
            Object obj = beansMap.get(key);
            if (obj == null) {
                throw new RuntimeException("no found");
            }
            //获取代理对象
            ProxyFactory proxyFactory = (ProxyFactory) beansMap.get("proxyFactory");
            beansMap.put(key,proxyFactory.getJdkProxy(obj));
        }
    }

    /**
     * 注入依赖
     * @throws Exception
     */
    private void injectionDependence() throws IllegalAccessException {

        for (Map.Entry<String, Object> entry : beansMap.entrySet()) {
            Class<? extends Object> classInfo = entry.getValue().getClass();
            //获取属性
            Field[] declaredFields = classInfo.getDeclaredFields();

            //当前类属性是否存在注解
            for (Field field : declaredFields) {
                //属性上有Autowired则注入
                Autowired annotation = field.getAnnotation(Autowired.class);
                if (annotation != null) {
                    Object bean = getBean(field.getName());
                    if (bean != null) {
                        field.setAccessible(true);
                        field.set(entry.getValue(), bean);
                    }
                }
            }
        }

    }

    /**
     * 首字母小写转换
     * @param s
     * @return
     */
    private String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

    public Object getBean(String id) {
        return beansMap.get(id);
    }

}
